package com.adamfass.tractorbeam;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import com.adamfass.jmeutil.Util;
import com.adamfass.tractorbeam.gameupdatelogic.DisruptorFieldFactory;
import com.adamfass.tractorbeam.gameupdatelogic.DisruptorPodTargeter;
import com.adamfass.tractorbeam.gameupdatelogic.DisruptorPodGameUpdateLogic;
import com.adamfass.tractorbeam.gameupdatelogic.TractorBeamGameUpdateLogic;
import com.adamfass.tractorbeam.gameupdatelogic.TractorBeamRecord;
import com.adamfass.tractorbeam.gameupdatelogic.TractorBeamTargeter;
import com.jme.input.KeyInput;
import com.jme.input.joystick.Joystick;
import com.jme.input.joystick.JoystickInput;
import com.jme.intersection.BoundingCollisionResults;
import com.jme.intersection.CollisionResults;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jmex.font2d.Font2D;
import com.jmex.font2d.Text2D;
import com.jmex.physics.PhysicsSpace;
import com.jmex.physics.StaticPhysicsNode;

/**
 * Players must try to get balls into goals.
 */
public class GoalGameFlow implements GameFlow {
  public static enum GameType {
    ONE_PLAYER_VS_COMPUTER,
    MULTIPLAYER
  }
  
  private final GameType gameType;
  
  private static final String RED_GOAL_NAME = "red goal";
  private static final String BLUE_GOAL_NAME = "blue goal";
  
  private Node goalParent;
  private ArrayList<Ball> balls;
  private Goal redGoal;
  private Goal blueGoal;
  
  private int redScore;
  private int blueScore;
  
  private Text2D redScoreText;
  private Text2D blueScoreText;
  
  private static final Random random = new Random(System.currentTimeMillis());
  
  private final ArrayList<PhysicsGameObject> targetList = 
    new ArrayList<PhysicsGameObject>();
  
  public GoalGameFlow(GameType gameType) {
    this.gameType = gameType;
  }
  
  public void setUp(GameObjectManager gameObjectManager, Node rootNode,
      PhysicsSpace physicsSpace, Set<GameUpdateLogic> gameUpdateLogicSet, 
      Set<Controller> controllerSet) {
    physicsSpace.setDirectionalGravity(new Vector3f(0, -9.8f * 8, 0));
    
    balls = new ArrayList<Ball>(2);
    
    goalParent = new Node();
    rootNode.attachChild(goalParent);
    
    redGoal = new Goal(goalParent, new Vector3f(-350, 100, 0), 25, 
                       ColorRGBA.green, RED_GOAL_NAME);
    blueGoal = new Goal(goalParent, new Vector3f(350, 100, 0), 25, 
                        ColorRGBA.blue, BLUE_GOAL_NAME);
    gameObjectManager.addNonTargettableGameEntity(redGoal);
    gameObjectManager.addNonTargettableGameEntity(blueGoal);
    
    redScore = 0;
    blueScore = 0;
    
    final Font2D font = new Font2D();
    final ZBufferState zBufferState = 
      DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
    zBufferState.setFunction(ZBufferState.TestFunction.Always);
    
    redScoreText = font.createText("" + redScore, 10, 0);
    redScoreText.setLocalScale(2);
    redScoreText.setLocalTranslation(new Vector3f(10, 10, 0));
    redScoreText.setRenderQueueMode(Renderer.QUEUE_ORTHO);
    redScoreText.setRenderState(zBufferState);
    rootNode.attachChild(redScoreText);
    
    blueScoreText = font.createText("" + blueScore, 10, 0);
    blueScoreText.setLocalScale(2);
    blueScoreText.setLocalTranslation(new Vector3f(
        DisplaySystem.getDisplaySystem().getRenderer().getWidth() - 50, 
        10, 0));
    blueScoreText.setRenderQueueMode(Renderer.QUEUE_ORTHO);
    blueScoreText.setRenderState(zBufferState);
    rootNode.attachChild(blueScoreText);
    
    final List<Joystick> joysticks = 
      Util.getRealJoysticks(JoystickInput.get());
    
    final HashSet<Blocker> blockerSet = new HashSet<Blocker>();
    final HashSet<TractorBeamRecord> tractorBeamRecordSet = 
      new HashSet<TractorBeamRecord>();
    
    if (gameType == GameType.ONE_PLAYER_VS_COMPUTER) {
      
      ////////////////////////////////////////////////////////////////////////////////////
      // Human setup
      
      final Controller humanController;
      if (joysticks.size() > 0) {
        humanController = new JoystickFlyingSaucerController(
            joysticks.get(0));
      } else {
        humanController = new KeyboardFlyingSaucerController(
            KeyInput.KEY_A, KeyInput.KEY_D, KeyInput.KEY_S, KeyInput.KEY_W,
            KeyInput.KEY_V, KeyInput.KEY_B);
      }
      controllerSet.add(humanController);
      
      final FlyingSaucer humanFlyingSaucer = new FlyingSaucer(
          rootNode, physicsSpace,
          new Vector3f(-100, 0, 0),
          ColorRGBA.green, humanController, gameObjectManager);
      gameObjectManager.addNonTargettableGameEntity(humanFlyingSaucer);
      blockerSet.add(humanFlyingSaucer);
      
      final TractorBeamTargeter humanTractorBeamTargeter = new TractorBeamTargeter(
          rootNode, humanFlyingSaucer, targetList);
      final TractorBeamGameUpdateLogic humanTractorBeamGameUpdateLogic = 
        new TractorBeamGameUpdateLogic(rootNode, humanController, humanFlyingSaucer, 
            humanTractorBeamTargeter, blockerSet, tractorBeamRecordSet);
      gameUpdateLogicSet.add(humanTractorBeamGameUpdateLogic);
      
      final DisruptorPodTargeter disruptorPodTargeter = new DisruptorPodTargeter(
          rootNode, humanFlyingSaucer, tractorBeamRecordSet);
      final DisruptorFieldFactory disruptorFieldFactory = 
        new DisruptorFieldFactory(rootNode, gameObjectManager, gameUpdateLogicSet, 
            blockerSet);
      final DisruptorPodGameUpdateLogic humanLogic = new DisruptorPodGameUpdateLogic(
          humanController, disruptorPodTargeter, disruptorFieldFactory);
      gameUpdateLogicSet.add(humanLogic);
      
      ////////////////////////////////////////////////////////////////////////////////////
      // AI setup
      
      final AiFlyingSaucerController aiController = 
        new AiFlyingSaucerController(gameObjectManager);
      controllerSet.add(aiController);
      controllerSet.add(humanController);
      final FlyingSaucer aiFlyingSaucer = new FlyingSaucer(
          rootNode, physicsSpace,
          new Vector3f(100, 0, 0),
          ColorRGBA.blue, aiController, gameObjectManager);
      aiController.setFlyingSaucer(aiFlyingSaucer);
      aiController.setGoal(blueGoal);
      
      final TractorBeamTargeter aiTractorBeamTargeter = new TractorBeamTargeter(
          rootNode, aiFlyingSaucer, targetList);
      final TractorBeamGameUpdateLogic aiTractorBeamGameUpdateLogic = 
        new TractorBeamGameUpdateLogic(rootNode, aiController, aiFlyingSaucer, 
            aiTractorBeamTargeter, blockerSet, tractorBeamRecordSet);
      gameUpdateLogicSet.add(aiTractorBeamGameUpdateLogic);
      
      gameObjectManager.addNonTargettableGameEntity(aiFlyingSaucer);
      blockerSet.add(aiFlyingSaucer);
    }
    
    final StaticPhysicsNode floor = physicsSpace.createStaticNode();
    floor.createBox("floor");
    floor.setLocalTranslation(new Vector3f(0, -600.5f, 0));
    floor.setLocalScale(new Vector3f(1800, 1, 1));
    floor.updateGeometricState(0, true);
    
    final StaticPhysicsNode ceiling = physicsSpace.createStaticNode();
    ceiling.createBox("ceiling");
    ceiling.setLocalTranslation(new Vector3f(0, 600.5f, 0));
    ceiling.setLocalScale(new Vector3f(1800, 1, 1));
    ceiling.updateGeometricState(0, true);
    
    final StaticPhysicsNode leftWall = physicsSpace.createStaticNode();
    leftWall.createBox("left wall");
    leftWall.setLocalTranslation(new Vector3f(-800.5f, 0, 0));
    leftWall.setLocalScale(new Vector3f(1, 1400, 1));
    leftWall.updateGeometricState(0, true);
    
    final StaticPhysicsNode rightWall = physicsSpace.createStaticNode();
    rightWall.createBox("right wall");
    rightWall.setLocalTranslation(new Vector3f(800.5f, 0, 0));
    rightWall.setLocalScale(new Vector3f(1, 1400, 1));
    rightWall.updateGeometricState(0, true);
    
    blockerSet.add(new LineObstacle(rootNode, physicsSpace, 
        new Vector3f(-300, 350, 0), new Vector3f(300, 350, 0), ColorRGBA.gray, "line"));

    blockerSet.add(new LineObstacle(rootNode, physicsSpace, 
        new Vector3f(-300, 200, 0), new Vector3f(-100, 200, 0), ColorRGBA.gray, "line"));
    blockerSet.add(new LineObstacle(rootNode, physicsSpace, 
        new Vector3f(100, 200, 0), new Vector3f(300, 200, 0), ColorRGBA.gray, "line"));
    
    blockerSet.add(new LineObstacle(rootNode, physicsSpace, 
        new Vector3f(-450, 200, 0), new Vector3f(-300, 350, 0), ColorRGBA.gray, "line"));
    blockerSet.add(new LineObstacle(rootNode, physicsSpace, 
        new Vector3f(450, 200, 0), new Vector3f(300, 350, 0), ColorRGBA.gray, "line"));
    
//    blockerSet.add(new LineObstacle(rootNode, physicsSpace, 
//        new Vector3f(-450, -300, 0), new Vector3f(450, -300, 0), ColorRGBA.gray, "line"));
    
    blockerSet.add(new LineObstacle(rootNode, physicsSpace,
        new Vector3f(-250, 100, 0), new Vector3f(-350, 0, 0), ColorRGBA.gray, "line"));
    blockerSet.add(new LineObstacle(rootNode, physicsSpace,
        new Vector3f(250, 100, 0), new Vector3f(350, 0, 0), ColorRGBA.gray, "line"));

    rootNode.updateGeometricState(0.0f, true);
    rootNode.updateRenderState();
  }
  
  private static float randomInRange(float lower, float upper) {
    return (float) (lower + (random.nextDouble() * (upper - lower)));
  }
  
  public void update(GameObjectManager gameObjectManager, Node rootNode,
      PhysicsSpace physicsSpace, Set<GameUpdateLogic> gameUpdateLogicSet) {
    while(balls.size() < 2) {
      final Vector3f ballPosition = new Vector3f(
          randomInRange(-400 + Ball.RADIUS, 400 - Ball.RADIUS),
          -600 + Ball.RADIUS,
          0);
      
      final Vector3f ballVelocity = new Vector3f(
          randomInRange(-400, 400),
          randomInRange(100, 400),
          0);
      
      final Ball ball = 
        new Ball(rootNode, physicsSpace, ballPosition, ballVelocity);
      gameObjectManager.addTargettableObject(ball);
      balls.add(ball);
      targetList.add(ball);
    }

    for (Ball ball : new ArrayList<Ball>(balls)) {
      final CollisionResults results = new BoundingCollisionResults();
      results.clear();
      ball.calculateCollisions(goalParent, results);
      if (results.getNumber() == 1) {
        if (results.getCollisionData(0).getTargetMesh().getName().equals(
            RED_GOAL_NAME)) {
          redScore++;
          redScoreText.setText("" + redScore);
        } else {
          blueScore++;
          blueScoreText.setText("" + blueScore);
        }
        balls.remove(ball);
        targetList.remove(ball);
        ball.markAsDeleted();
        gameObjectManager.removeTargettableObject(ball);
      }
    }
    
    DisplaySystem.getDisplaySystem().getRenderer().getCamera()
    .setParallelProjection(false);
    DisplaySystem.getDisplaySystem().getRenderer().getCamera().setLocation(
        new Vector3f(0, 0, 10000));
    DisplaySystem.getDisplaySystem().getRenderer().getCamera().setFrustum(
        9900, 20000, -800, 800, 600, -600);
  }
}